package com.kalvan.web.feign;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kalvan.client.exception.ReturnCode;
import com.kalvan.web.feign.cache.FeignCacheService;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * 接口返回码转换
 *
 * @author chenliang
 */
@Slf4j
public class FeignResponseDecoder implements Decoder {
    private final Decoder decoder;
    @Resource
    private FeignCacheService feignCacheService;

    public FeignResponseDecoder() {
        log.info("init FeignResponseDecoder...");
        //系统接口未正确设置Content-Type值，需要自定义Feign Decoder去兼容各种Content-Type
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(objectMapper);
        List<MediaType> supportedMediaTypes = jacksonConverter.getSupportedMediaTypes();
        List<MediaType> mediaTypes = new ArrayList<>();
        if (!CollectionUtils.isEmpty(supportedMediaTypes)) {
            mediaTypes.addAll(supportedMediaTypes);
        }
        mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
        mediaTypes.add(MediaType.TEXT_PLAIN);
        jacksonConverter.setSupportedMediaTypes(mediaTypes);
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
        this.decoder = new SpringDecoder(objectFactory);
    }

    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {
        Object decode = innerDecode(response, type);
        if (decode instanceof com.kalvan.client.model.Response) {
            //转换返回码
            com.kalvan.client.model.Response resp = (com.kalvan.client.model.Response) decode;
            Map<String, ReturnCode> retCodeConvertMap = feignCacheService.getReturnCodeMap(resp.getSystem());
            ReturnCode returnCode = retCodeConvertMap.get(resp.getCode());
            if (returnCode != null) {
                resp.setCode(returnCode.getCode());
                resp.setDesc(returnCode.getDesc());
            }
        }
        return decode;
    }


    private Object innerDecode(final Response response, Type type)
            throws IOException, FeignException {
        if (isParameterizeHttpEntity(type)) {
            type = ((ParameterizedType) type).getActualTypeArguments()[0];
            Object decodedObject = this.decoder.decode(response, type);
            return createResponse(decodedObject, response);
        } else if (isHttpEntity(type)) {
            return createResponse(null, response);
        } else {
            return this.decoder.decode(response, type);
        }
    }

    private boolean isParameterizeHttpEntity(Type type) {
        if (type instanceof ParameterizedType) {
            return isHttpEntity(((ParameterizedType) type).getRawType());
        }
        return false;
    }

    private boolean isHttpEntity(Type type) {
        if (type instanceof Class) {
            Class c = (Class) type;
            return HttpEntity.class.isAssignableFrom(c);
        }
        return false;
    }

    private <T> ResponseEntity<T> createResponse(Object instance, Response response) {
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        for (String key : response.headers().keySet()) {
            headers.put(key, new LinkedList<>(response.headers().get(key)));
        }
        return new ResponseEntity<>((T) instance, headers,
                HttpStatus.valueOf(response.status()));
    }
}
